/**
 * 
 */
package uk.ac.ebi.velvet.util.config;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.beanutils.BeanUtils;

import uk.ac.ebi.velvet.config.VelvetConfig;
import uk.ac.ebi.velvet.util.config.impl.MapConfigResource;
import uk.ac.ebi.velvet.util.config.impl.PropertyfileConfigResource;

/**
 * @author mhaimel
 *
 */
public class ConfigFactory {
	private static final ConfigFactory factory = new ConfigFactory();
	private List<ConfigResource> resList = new ArrayList<ConfigResource>();
	
	public static void main(String[] args) {
		ConfigFactory cf = ConfigFactory.instance();
		MapConfigResource mcf = new MapConfigResource();
		Map<Object, Object> m = new HashMap<Object, Object>();
		m.put("test", "Hello World");
		m.put("testMe", "Hello Map!");
		mcf.register(VelvetConfig.class, m);
		
		cf.addResource(mcf);
		ConfigFactory.enablePropertyResource();
		
		VelvetConfig conf = ConfigFactory.create(VelvetConfig.class);
		System.out.println("msg: >" + conf + "<");
	}
	
	private ConfigFactory() {
		// do nothing
	}	
	private ConfigFactory(ConfigResource ... cArr) {
		resList.addAll(Arrays.asList(cArr));
	}
	
	public static ConfigFactory instance(){
		return factory;
	}
	
	
	public <T> T createClass(Class<T> clazz) throws Error{
		Properties prop = getProperties(clazz);
		T bean = createEmptyBean(clazz);
		populateBean(bean,prop);
		return bean;
	}
	
	private void populateBean(Object bean,Properties prop) throws Error{
		try {
			BeanUtils.populate(bean, prop);
		} catch (IllegalAccessException e) {
			throw new Error("Not able to populate bean with data:"+bean.getClass()+"; data: " + prop ,e);
		} catch (InvocationTargetException e) {
			throw new Error("Not able to populate bean with data:"+bean.getClass()+"; data: " + prop ,e);
		}
	}
	
	private <T> T createEmptyBean(Class<T> clazz) {
		try {
			Constructor<T> con = clazz.getConstructor();
			T bean = con.newInstance();
			return bean;
		} catch (Exception e) {
			throw new Error("Not able to create new instance of class "+clazz ,e);
		}
	}

	private Properties getProperties(Class<?> clazz) {
		Properties prop = new Properties();
		for(ConfigResource res : resList){
			Properties nProp = res.getProperties(clazz);
			for(String key : nProp.stringPropertyNames()){
				prop.setProperty(key, nProp.getProperty(key));
			}
		}
		return prop;
	}
	
	public Properties extractProperties(Object bean) throws Error{
		Properties prop = new Properties();
		try {
			Map map = BeanUtils.describe(bean);
			prop.putAll(map);
		} catch (Exception e) {
			throw new Error(
					"Not able to extract properties from bean " + bean);
		}
		return prop;	
	}
	
	public void addResource(ConfigResource res){
		this.resList.add(res);
	}
	
	public void populate(Object bean){
		Properties prop = getProperties(bean.getClass());
		populateBean(bean, prop);
	}
	
	public static void enablePropertyResource(){
		instance().addResource(new PropertyfileConfigResource());
	}	
	
	public static <T> T create(Class<T> clazz) throws Error{
		return instance().createClass(clazz);
	}
	
	public static void populateBean(Object bean)throws Error{
		instance().populate(bean);
	}

	public static Properties asProperties(Object o) throws Error{
		return instance().extractProperties(o);
	}	
}
